/* Copyright 2013-present Facebook, Inc.
 * Licensed under the Apache License, Version 2.0 */

#include "watchman.h"

/* Query evaluator */

static const w_string& compute_parent_path(
    struct w_query_ctx* ctx,
    const watchman_file* file) {
  if (ctx->last_parent == file->parent) {
    return ctx->last_parent_path;
  }

  ctx->last_parent_path = file->parent->getFullPath();
  ctx->last_parent = file->parent;

  return ctx->last_parent_path;
}

w_string_t *w_query_ctx_get_wholename(
    struct w_query_ctx *ctx
)
{
  uint32_t name_start;

  if (ctx->wholename) {
    return ctx->wholename;
  }

  if (ctx->query->relative_root) {
    // At this point every path should start with the relative root, so this is
    // legal
    name_start = ctx->query->relative_root.size() + 1;
  } else {
    name_start = ctx->root->root_path.size() + 1;
  }

  auto full_name = w_string::pathCat(
      {compute_parent_path(ctx, ctx->file), ctx->file->getName()});

  // Record the name relative to the root
  ctx->wholename = full_name.slice(name_start, full_name.size() - name_start);

  return ctx->wholename;
}

bool w_query_process_file(
    w_query* query,
    struct w_query_ctx* ctx,
    const watchman_file* file) {
  ctx->wholename.reset();
  ctx->file = file;

  // For fresh instances, only return files that currently exist.
  if (!ctx->since.is_timestamp && ctx->since.clock.is_fresh_instance &&
      !file->exists) {
    return true;
  }

  // We produce an output for this file if there is no expression,
  // or if the expression matched.
  if (query->expr && !query->expr->evaluate(ctx, file)) {
    // No matched
    return true;
  }

  if (ctx->query->dedup_results) {
    w_string_t *name = w_query_ctx_get_wholename(ctx);

    auto inserted = ctx->dedup.insert(name);
    if (!inserted.second) {
      // Already present in the results, no need to emit it again
      ctx->num_deduped++;
      return true;
    }
  }

  bool is_new;
  if (ctx->since.is_timestamp) {
    is_new = ctx->since.timestamp > file->ctime.timestamp;
  } else if (ctx->since.clock.is_fresh_instance) {
    is_new = true;
  } else {
    is_new = file->ctime.ticks > ctx->since.clock.ticks;
  }

  watchman_rule_match match(
      ctx->clockAtStartOfQuery.rootNumber,
      w_query_ctx_get_wholename(ctx),
      is_new,
      file);

  json_array_append_new(
      ctx->resultsArray, file_result_to_json(ctx->query->fieldList, match));

  return true;
}

void w_match_results_free(uint32_t num_matches,
    struct watchman_rule_match *matches)
{
  uint32_t i;

  for (i = 0; i < num_matches; i++) {
    w_string_delref(matches[i].relname);
  }
  free(matches);
}

bool w_query_file_matches_relative_root(
    struct w_query_ctx* ctx,
    const watchman_file* f) {
  bool result;

  if (!ctx->query->relative_root) {
    return true;
  }

  auto parent_path = compute_parent_path(ctx, f);
  // "in relative root" here does not mean exactly the relative root, so compare
  // against the relative root's parent.
  result = w_string_equal(parent_path, ctx->query->relative_root) ||
           w_string_startswith(parent_path, ctx->query->relative_root_slash);

  return result;
}

bool time_generator(
    w_query* query,
    const std::shared_ptr<w_root_t>& root,
    struct w_query_ctx* ctx,
    int64_t* num_walked) {
  return root->inner.view->timeGenerator(query, ctx, num_walked);
}

static bool default_generators(
    w_query* query,
    const std::shared_ptr<w_root_t>& root,
    struct w_query_ctx* ctx,
    int64_t* num_walked) {
  bool generated = false;
  int64_t n = 0;
  int64_t total = 0;
  bool result = true;

  // Time based query
  if (ctx->since.is_timestamp || !ctx->since.clock.is_fresh_instance) {
    n = 0;
    result = time_generator(query, root, ctx, &n);
    total += n;
    if (!result) {
      goto done;
    }
    generated = true;
  }

  // Suffix
  if (!query->suffixes.empty()) {
    n = 0;
    result = root->inner.view->suffixGenerator(query, ctx, &n);
    total += n;
    if (!result) {
      goto done;
    }
    generated = true;
  }

  if (!query->paths.empty()) {
    n = 0;
    result = root->inner.view->pathGenerator(query, ctx, &n);
    total += n;
    if (!result) {
      goto done;
    }
    generated = true;
  }

  if (query->glob_tree) {
    n = 0;
    result = root->inner.view->globGenerator(query, ctx, &n);
    total += n;
    if (!result) {
      goto done;
    }
    generated = true;
  }

  // And finally, if there were no other generators, we walk all known
  // files
  if (!generated) {
    n = 0;
    result = root->inner.view->allFilesGenerator(query, ctx, &n);
    total += n;
    if (!result) {
      goto done;
    }
  }

done:
  *num_walked = total;
  return result;
}

w_query_res::~w_query_res() {
  free(errmsg);
}

static bool execute_common(
    struct w_query_ctx* ctx,
    w_perf_t* sample,
    w_query_res* res,
    w_query_generator generator) {
  int64_t num_walked = 0;
  bool result = true;

  if (ctx->query->dedup_results) {
    ctx->dedup.reserve(64);
  }

  res->is_fresh_instance = !ctx->since.is_timestamp &&
    ctx->since.clock.is_fresh_instance;

  if (!(res->is_fresh_instance && ctx->query->empty_on_fresh_instance)) {
    if (!generator) {
      generator = default_generators;
    }

    if (!generator(ctx->query, ctx->root, ctx, &num_walked)) {
      res->errmsg = ctx->query->errmsg;
      ctx->query->errmsg = NULL;
      result = false;
    }
  }

  if (sample->finish()) {
    sample->add_root_meta(ctx->root);
    sample->add_meta(
        "query_execute",
        json_object(
            {{"fresh_instance", json_boolean(res->is_fresh_instance)},
             {"num_deduped", json_integer(ctx->num_deduped)},
             {"num_results", json_integer(json_array_size(ctx->resultsArray))},
             {"num_walked", json_integer(num_walked)},
             {"query", ctx->query->query_spec}}));
    sample->log();
  }

  res->resultsArray = ctx->resultsArray;
  res->dedupedFileNames = std::move(ctx->dedup);

  return result;
}

w_query_ctx::w_query_ctx(w_query* q, const std::shared_ptr<w_root_t>& root)
    : query(q), root(root), resultsArray(json_array()) {
  // build a template for the serializer
  if (query->fieldList.size() > 1) {
    json_array_set_template_new(
        resultsArray, field_list_to_json_name_array(query->fieldList));
  }
}

bool w_query_execute_locked(
    w_query* query,
    const std::shared_ptr<w_root_t>& root,
    w_query_res* res,
    w_query_generator generator) {
  w_query_ctx ctx(query, root);
  w_perf_t sample("query_execute");

  /* The first stage of execution is generation.
   * We generate a series of file inputs to pass to
   * the query executor.
   *
   * We evaluate each of the generators one after the
   * other.  If multiple generators are used, it is
   * possible and expected that the same file name
   * will be evaluated multiple times if those generators
   * both emit the same file.
   */

  ctx.clockAtStartOfQuery =
      root->inner.view->getMostRecentRootNumberAndTickValue();
  res->clockAtStartOfQuery = ctx.clockAtStartOfQuery;

  // Evaluate the cursor for this root
  w_clockspec_eval(root, query->since_spec.get(), &ctx.since);

  return execute_common(&ctx, &sample, res, generator);
}

bool w_query_execute(
    w_query* query,
    const std::shared_ptr<w_root_t>& root,
    w_query_res* res,
    w_query_generator generator) {
  w_query_ctx ctx(query, nullptr);

  w_perf_t sample("query_execute");

  if (query->sync_timeout.count() && !root->syncToNow(query->sync_timeout)) {
    ignore_result(asprintf(&res->errmsg, "synchronization failed: %s\n",
        strerror(errno)));
    return false;
  }

  /* The first stage of execution is generation.
   * We generate a series of file inputs to pass to
   * the query executor.
   *
   * We evaluate each of the generators one after the
   * other.  If multiple generators are used, it is
   * possible and expected that the same file name
   * will be evaluated multiple times if those generators
   * both emit the same file.
   */

  ctx.root = root;
  // Evaluate the cursor for this root
  w_clockspec_eval(root, query->since_spec.get(), &ctx.since);

  ctx.clockAtStartOfQuery =
      root->inner.view->getMostRecentRootNumberAndTickValue();
  res->clockAtStartOfQuery = ctx.clockAtStartOfQuery;

  return execute_common(&ctx, &sample, res, generator);
}

/* vim:ts=2:sw=2:et:
 */
